//@author A0088498A
package logic;

import gui.IUserInterface;
import hirondelle.date4j.DateTime;

import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

import common.AttributeType;
import common.Command;
import common.CommandType;
import common.DateHelper;
import common.Task;
import common.TaskType;

import memory.History;
import memory.Database;

/**
 * The Command Handler holds methods that all commands will need to access <br/>
 * 
 */
public class CommandHandler {
	/**
	 * Reference to the UI that execute the command
	 */
	protected IUserInterface _ui;

	/**
	 * The parsed Attribute List which map the defined Attribute Type with its
	 * value
	 */
	protected HashMap<AttributeType, String> _attrList;

	/**
	 * The raw Command from user
	 */
	protected String _originalCommand;

	protected final String NO_RESULT = "\nNo result found\n";

	/**
	 * Empty CommandHandler
	 */
	public CommandHandler() {
		_ui = null;
		_originalCommand = null;
		_attrList = null;
	}

	/**
	 * Standard Constructor<br/>
	 * originalCommand is passed for storage purpose<br/>
	 * attrList is passed for creating new Task<br/>
	 * 
	 * @param originalCommand
	 * @param attrList
	 */
	public CommandHandler(IUserInterface ui, String originalCommand, HashMap<AttributeType, String> attrList) {
		_ui = ui;
		_originalCommand = originalCommand;
		_attrList = attrList;

	}

	/**
	 * Search for task name in Storage and return List<Task>
	 * 
	 * @param keys
	 *            the list of String to be search
	 * @return the list of Tasks with contains the all the keys
	 */
	protected List<Task> searchFromStorage(boolean isAsc, String... keys) {
		return getStorage().searchTask(isAsc, keys);
	}

	/**
	 * When the taskName provided by the users is referred to multiple tasks,
	 * display the result in console for users to confirm the exact Tasks to
	 * process <br/>
	 * 
	 * @param result
	 *            list of Tasks
	 * @param prmpt
	 *            the heading of the list
	 */
	protected void passResultToConsole(List<Task> result, String prmpt) {
		_ui.writeMessage(prmpt);
		_ui.writeTasks(result);
	}

	/**
	 * Display the Free slots to the Console
	 * 
	 * @param freeSlots
	 *            the List of Free slots in form of Tasks
	 * @param prmpt
	 *            the heading of the list
	 */
	protected void passFreeSlotsToConsole(List<Task> freeSlots, String prmpt) {
		_ui.writeMessage(prmpt);
		_ui.writeFreeSlots(freeSlots);
	}

	/**
	 * Write a message to console
	 * 
	 * @param msg
	 *            the message to be printed
	 * @param args
	 *            (optional) the arguments to be formatted with the message
	 */
	protected void writeToConsole(String msg, String... args) {
		_ui.writeMessage(msg, args);
	}

	/**
	 * Record a command with a specified type to History <br/>
	 * There is no Task pushing to the History, hence null will be pushed
	 * instead <br/>
	 * This is same as calling <b>pushCommandToHistory(type, null)</b><br/>
	 * 
	 * @param type
	 *            the command type to be recorded
	 */
	protected void pushCommandToHistory(CommandType type) {
		pushCommandToHistory(type, null);
	}

	//@author A0097972J
	/**
	 * Record a command with a specified type to History
	 * 
	 * @param type
	 *            the command type to be recorded
	 * @param task
	 *            The task to be recorded
	 */
	protected void pushCommandToHistory(CommandType type, Task task) {
		Command newCommand = new Command(_originalCommand, type, task);
		getHistory().pushCmd(newCommand);
	}

	//@author A0097972J
	/**
	 * Covert the text String into format supported by DateTime, yyyy-MM-dd.<br/>
	 * if String is not parse-able, try convert to calendar object and format
	 * back to yyyy-MM-dd <br/>
	 * 
	 * @param text
	 *            the String holding the parse-able date and time
	 * @return the DateTime object. Null if cannot parse
	 */
	public DateTime getDateTime(String text) {
		if (text == null) {
			return null;
		} else {
			DateHelper dh = new DateHelper();
			Calendar tempCal = dh.DateFormatter(text.trim());
			if (tempCal != null) {

				String checkYear = dh.checkSupportingYear(tempCal.get(Calendar.YEAR));

				if (checkYear != null) {
					writeToConsole(checkYear);
					return null;
				}

				return dh.processGetDateTime(tempCal, isEndingTime(text));
			}
			return null;
		}
	}

	//@author A0097972J
	/**
	 * Check whether a String represents the Ending Time of this Task <br/>
	 * 
	 * @param endingTime
	 * @return true if the string equals to the string in the Attribute List
	 */
	private boolean isEndingTime(String endingTime) {
		try {
			return endingTime.equals(_attrList.get(AttributeType.endingTime));
		} catch (NullPointerException e) {
			// This command provides no ending Time
			return false;
		}
	}

	//@author A0097972J
	/**
	 * Push unsuccessful task created to history as non-executable or error
	 * commands
	 */
	protected void pushToHistoryAsNonExecutableCommand() {
		// Use CommandType as display for non-executable command
		pushCommandToHistory(CommandType.invalid, null);
	}

	//@author A0088498A
	/**
	 * Get the reference for the storage module
	 * 
	 * @return Storage
	 */
	protected Database getStorage() {
		Database storage = _ui.getStorage();
		return storage;
	}

	/**
	 * Get the reference for the history module
	 * 
	 * @return History
	 */
	protected History getHistory() {
		History history = _ui.getHistory();
		return history;
	}

	//@author A0097972J
	/**
	 * Determine the taskType based on the information provided in the
	 * startingTime, endingTime and range fields <br/>
	 * 
	 * @return the possible taskType
	 */

	protected static TaskType determineTaskType(String startingTime, String endingTime, String range) {

		if (startingTime == null && endingTime == null && range == null) {
			return TaskType.floating;
		} else if (startingTime == null && endingTime != null) {
			return TaskType.deadline;
		} else if (startingTime != null && (endingTime != null || range != null)) {
			return TaskType.timed;
		} else {
			return TaskType.invalid;
		}
	}
}